#include "environment.h"
#include <iostream>
#include <stdlib.h>
#include "animal.h"

using namespace std;
	
int Environment::mapSizeX = 11;
int Environment::mapSizeY = 11;

Environment::Environment() {
	predStart=0;
	preyStart=0;
	print = false;
}

// Deletes both the predator and prey lists
Environment::~Environment() {
	clearWrapperLists();
}

// Sets the print mode for the simulations
void Environment::setPrint( bool printMode ) {
	print = printMode;
}

// Deletes the wrapper lists, but keeps the animals in memory
void Environment::clearWrapperLists() {
	if ( predStart ) {
		while( predStart->next != 0 ) {
			predStart = predStart->next;
			delete predStart->prev;
		}
		delete predStart;
		predStart = 0;
	}

	if ( preyStart ) {
		while( preyStart->next != 0 ) {
			preyStart = preyStart->next;
			delete preyStart->prev;
		}
		delete preyStart;
		preyStart = 0;
	}
}

// Useful method to deallocate all the agents in the environment
void Environment::deleteAllAgents() {
	AnimalWrapper * helper;
	
	helper = predStart;
	while( helper ) {
		delete helper->animal;
		helper = helper->next;
	}
	
	helper = preyStart;
	while( helper ) {
		delete helper->animal;
		helper = helper->next;
	}

	// Finishes by deleting all the wrappers
	clearWrapperLists();
}

// Adds a new predator to the predator list
void Environment::addPredator(Animal * pred, int x, int y) {
	addAnimal(pred, x, y, 0);
}

// Adds a new prey to the prey list
void Environment::addPrey(Animal * prey, int x, int y) {
	addAnimal(prey, x, y, 1 );
}

// Adds an animal to one of the agents lists
void Environment::addAnimal(Animal * an, int x, int y, int type) {
	AnimalWrapper ** helper;
	if ( type == 0 ) helper = &predStart;
	else if ( type == 1 ) helper = &preyStart;
	else return;

	AnimalWrapper * newAnimal = new AnimalWrapper();
	newAnimal->animal = an;
	newAnimal->startPosition[0]=x;
	newAnimal->startPosition[1]=y;
	newAnimal->position[0]=x;
	newAnimal->position[1]=y;
	newAnimal->next=0;

	if( ! (*helper) ) {
		*helper = newAnimal;
		newAnimal->prev=0;
	}
	else{
		while((*helper)->next != 0)	{
			// Check if we don't already own this animal in the list
			// The user must instantiate different animals, can't use the same more than once!
			if ( (*helper)->animal == an ) {
				delete newAnimal;
				return;
			}
			helper = &((*helper)->next);
		}
		// Last element check
		if ( (*helper)->animal == an ) {
			delete newAnimal;	
			return;
		}

		(*helper)->next = newAnimal;
		newAnimal->prev= *helper;
	}

	// If we are here we know we added the new animal in the Environment
	Animal::increaseNumberOfAgents();
}

// Resets the position for all the animals as they were first put in the environment
void Environment::resetAgentsPositions() {
	AnimalWrapper * current = predStart;
	
	vector<int> stateVector;

	while(current != 0) {
		current->position[0]=current->startPosition[0];
		current->position[1]=current->startPosition[1];

		stateVector.push_back(current->startPosition[0]);	
		stateVector.push_back(current->startPosition[1]);	

		current = current->next;
	}
	
	current = preyStart;
	while(current != 0) {
		current->position[0]=current->startPosition[0];
		current->position[1]=current->startPosition[1];

		stateVector.push_back(current->startPosition[0]);	
		stateVector.push_back(current->startPosition[1]);	

		current = current->next;
	}

	// Now we know the whole initial state, and we can it as last state	
	int state = Animal::stateConverter(stateVector);

	current = predStart;
	while(current != 0) {
		current->animal->setLastState(state);
		current = current->next;
	}
	
	current = preyStart;
	while(current != 0) {
		current->animal->setLastState(state);
		current = current->next;
	}
}

// The actual simulation cycle
int Environment::startSimulation() {
	// Stop Simulation if no animals exist
	if ( ! predStart || ! preyStart ) return -1; 

	int time = 1;
	AnimalWrapper * helper, * helper2, * ender;
	int counter, counter2, move, trip;
	helper = predStart;

	int singlePredMove, singlePreyMove;

	vector<int> stateVector;

	resetAgentsPositions();

	if ( Animal::getAlpha() == 0.0 ) {
		cout << "Resetting alpha parameter...\n";
		Animal::setAlpha(1.0);
	}
	
	while (1) {
		if ( print ) cout << "***** TIME " << time << " *****\n";

		// Moving all the predators
		helper = predStart;
		counter =1;

		while(helper) {
			if ( print ) cout << "Predator " << counter << ":\t" << helper->position[0]
                 << "," << helper->position[1] <<"\t->\t";

			// Predator executes move
			move = helper->animal->move();
			singlePredMove = move;

			// Position updated in the environment
			updatePosition(helper, move);
			
			// Saving next state representation
			stateVector.push_back( helper->position[0] );
			stateVector.push_back( helper->position[1] );

			if ( print ) { 
				cout << helper->position[0] << "," << helper->position[1] << 
						"\tMove " << move;
				if ( helper->animal->isActionExploratory() )
					cout << "\tExploratory!";

				cout << "\n";
			}

			//Next predator
			helper = helper->next;
			counter++;
		}

		// Moving all the preys
		helper = preyStart;
		counter = 1;

		while(helper) {
			if ( print ) cout << "Prey " << counter << ":\t\t" << helper->position[0]
                 << "," << helper->position[1] << "\t->\t";

			// Prey executes move
			move = helper->animal->move();
			singlePreyMove = move;

			// We check if the prey has tripped ( 0.2 chance )
			trip = ( rand() % 5 );
			if ( !trip ) move = 4;

			// Environment updates prey position
			updatePosition(helper, move);

			// Saving next state representation
			stateVector.push_back( helper->position[0] );
			stateVector.push_back( helper->position[1] );
			
			if ( print ) { 
				cout << helper->position[0] << "," << helper->position[1] << 
						"\tMove " << move;
				if ( helper->animal->isActionExploratory() )
					cout << "\tExploratory!";
				if ( !trip )
					cout << "\tTripped!";

				cout << "\n";
			}
 
			// Next prey
			helper = helper->next;
			counter++;
		}

		// Checking if the simulation is over
		int state = Animal::stateConverter(stateVector);
		stateVector.clear();

		
		// Position check for the predators
		helper = predStart;
		counter = 1;

		while ( helper ) {
			helper2 = helper->next;
			counter2 = counter + 1;
			
			while ( helper2 ) {
				if( ( helper->position[0] == helper2->position[0]) &&
					( helper->position[1] == helper2->position[1]) ) {
					// Predators have lost the game
					if ( print ) cout << "\nThe Predator " << counter << " has collided with Predator " << counter2 << "!\n";

					// End the game as a loss for the predators
					ender = predStart;
					while ( ender ) {
						ender->animal->setObservation( state, -10 );
						ender->animal->setTerminalState();
						ender = ender-> next;
					}

					// End the game as a loss for the preys
					ender = preyStart;
					while ( ender ) {
						ender->animal->setObservation( state, 10 );
						ender->animal->setTerminalState();
						ender = ender-> next;
					}
					
					// Resets all the animals in the environment at their starting position
					return time;
				}
				helper2 = helper2->next;
				counter2++;
			}

			helper = helper->next;
			counter++;
		}

		// Catch check
		helper = predStart;
		counter = 1;

		// WARNING: only works for 1 prey!
		while ( helper ) {
			if( ( helper->position[0] == preyStart->position[0]) &&
				( helper->position[1] == preyStart->position[1]) ) {
				// Predators have won the game
				if ( print ) cout << "\nThe Predator " << counter << " has reached the Prey!\n";

				// End the game as a loss for the predators
				ender = predStart;
				while ( ender ) {
					ender->animal->setObservation( state, 10, singlePreyMove );
					ender->animal->setTerminalState(singlePreyMove);
					ender = ender-> next;
				}

				// End the game as a loss for the preys
				ender = preyStart;
				while ( ender ) {
					ender->animal->setObservation( state, -10, singlePredMove );
					ender->animal->setTerminalState(singlePredMove);
					ender = ender-> next;
				}

				// Resets all the animals in the environment at their starting position
				return time;
			}
			helper = helper->next;
			counter++;
		}
		// End the game as a loss for the predators
		ender = predStart;
		while ( ender ) {
			ender->animal->setObservation( state, 0, singlePreyMove );
			ender = ender-> next;
		}

		// End the game as a loss for the preys
		ender = preyStart;
		while ( ender ) {
			ender->animal->setObservation( state, 0, singlePredMove );
			ender = ender-> next;
		}
		
		if ( Animal::getAlgorithm() == 2 ) {Animal::decayAlpha();
			if ( Animal::getAlpha() == 0.0 ) {
				cout << "The learning parameter reached zero! The next simulation will reset it to one!\n";
				return time;
			}
		}

		time++;
	}	// Time cycle
	return time;
}

// Moves an animal based on its move
void Environment::updatePosition(AnimalWrapper * animal, int direction) {
	switch(direction) {
		case 0:	animal->position[1]=(animal->position[1]+1)%mapSizeY;
				break;
		case 1:	animal->position[0]=(animal->position[0]+1)%mapSizeX;
				break;
		case 2:	animal->position[1]=(animal->position[1]+(mapSizeY-1))%mapSizeY;
				break;
		case 3: animal->position[0]=(animal->position[0]+(mapSizeX-1))%mapSizeX;
				break;
		default:;
			// We don't move
	}
}

